Write a custom CUDA kernel to optimize `Tversky Loss`.

Formula:
Index_c = (TP_c + smooth) / (TP_c + alpha * FP_c + beta * FN_c + smooth)
Loss = 1 - mean(Index_c)
Where:
- TP_c = sum(p_c * g_c)
- FP_c = sum(p_c * (1 - g_c))
- FN_c = sum((1 - p_c) * g_c)
- p is softmax probability, g is one-hot target.

Problem Analysis:
1. Memory Usage: Standard implementation materializes large (N, C, Spatial) tensors for Probability and One-Hot targets. For 3D volumetric data, this is extremely expensive.
2. Bandwidth: Calculating sums for TP, FP, FN involves multiple passes over these large tensors.

Optimization Strategy: Fused Softmax-Accumulation Kernel

1. Parallelism: One Block per Sample (Batch element). Threads iterate over spatial positions (Grid-Stride Loop).

2. On-the-Fly Softmax:
   For each spatial voxel:
   - Read logits for all classes.
   - Compute Softmax (Max + SumExp) locally.
   - Read target class index.

3. Fused Accumulation:
   Instead of full TP/FP/FN tensors, accumulate sufficient statistics in registers/shared memory:
   - `sum_intersection`: sum(p_c) where c == target.
   - `sum_p`: sum(p_c) for all c.
   - `sum_g`: count(c == target).
   
   From these:
   - TP_c = sum_intersection[c]
   - FP_c = sum_p[c] - TP_c
   - FN_c = sum_g[c] - TP_c

4. Reduction & Composition:
   - Perform block-level reduction for the per-class statistics.
   - Thread 0 computes the Tversky Index and final Loss.
  
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn
import torch.nn.functional as F

BATCH_SIZE = 16
NUM_CLASSES = 4
DEPTH = 64
HEIGHT = 128
WIDTH = 128
SPATIAL_DIM = DEPTH * HEIGHT * WIDTH 
SHAPE_LOGITS = (BATCH_SIZE, NUM_CLASSES, DEPTH, HEIGHT, WIDTH)
SHAPE_TARGET = (BATCH_SIZE, DEPTH, HEIGHT, WIDTH)

ALPHA = 0.7
BETA = 0.3
SMOOTH = 1e-6
REDUCTION = 'none'

class TverskyLoss(nn.Module):
    """
    Tversky loss function for image segmentation using 3D fully convolutional deep networks
    https://arxiv.org/pdf/1706.05721
    """
    def __init__(self, alpha=0.7, beta=0.3, smooth=1e-6, reduction='mean'):
        super(TverskyLoss, self).__init__()
        self.alpha = alpha
        self.beta = beta
        self.smooth = smooth
        self.reduction = reduction

    def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
        # logits: (B, C, D, H, W)
        # targets: (B, D, H, W)
        
        probs = F.softmax(logits, dim=1)

        targets_onehot = F.one_hot(targets.long(), num_classes=logits.shape[1])
        targets_onehot = targets_onehot.permute(0, 4, 1, 2, 3).float()
        
        dims = (2, 3, 4)
        
        tp = torch.sum(probs * targets_onehot, dim=dims)
        fp = torch.sum(probs * (1.0 - targets_onehot), dim=dims)
        fn = torch.sum((1.0 - probs) * targets_onehot, dim=dims)
        
        # Tversky Index
        numerator = tp + self.smooth
        denominator = tp + self.alpha * fp + self.beta * fn + self.smooth
        
        score = numerator / denominator
        
        # Loss = 1 - mean(score)
        loss = 1.0 - score.mean(dim=1)
        
        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        return loss

class Model(nn.Module):
    def __init__(self, alpha=0.7, beta=0.3, smooth=1e-6, reduction='none'):
        super(Model, self).__init__()
        self.loss_fn = TverskyLoss(alpha, beta, smooth, reduction)
    
    def forward(self, logits, targets):
        return self.loss_fn(logits, targets)

def get_inputs():
    logits = torch.randn(SHAPE_LOGITS, dtype=torch.float32)
    targets = torch.randint(0, NUM_CLASSES, SHAPE_TARGET, dtype=torch.long)
    return [logits.contiguous(), targets.contiguous()]

def get_init_inputs():
    return [ALPHA, BETA, SMOOTH, REDUCTION]